#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
RAG服务简单实现模块

该模块提供了一个简化版的RAG(Retrieval-Augmented Generation)服务，主要功能包括：
1. 文档解析（支持PDF、TXT、DOCX、DOC、MD、JSON、CSV等格式）
2. 文档分块和向量化
3. 向量存储管理（基于ChromaDB）
4. 文档检索与过滤
5. 集合管理（创建、清空、查询）

通过用户ID和课程ID实现多租户隔离，支持多种文档格式的导入和检索。
"""
from pathlib import Path
from typing import List, Optional, Tuple
import os
import uuid

# 禁用ChromaDB遥测，减少错误日志输出
os.environ["ANONYMIZED_TELEMETRY"] = "False"

# 导入所需的库
from langchain_community.vectorstores import Chroma  # 向量数据库
from langchain_community.embeddings import SentenceTransformerEmbeddings  # 嵌入模型
from langchain_community.document_loaders import PyPDFLoader, TextLoader  # 文档加载器
from langchain.text_splitter import RecursiveCharacterTextSplitter  # 文本分块工具
try:
    from chromadb.config import Settings as ChromaSettings  # Chroma配置（可选）
except Exception:
    ChromaSettings = None  # 兼容旧版本，无需强行引入

class RagService:
    """
    RAG服务核心类
    
    负责管理文档的解析、向量化、存储和检索，支持多租户隔离，基于ChromaDB实现高效的向量检索。
    """
    def __init__(self, persist_dir: str = "app/.chroma", model_name: str = "paraphrase-multilingual-MiniLM-L12-v2"):
        """
        初始化RAG服务
        
        Args:
            persist_dir: 向量数据库持久化目录
            model_name: 使用的嵌入模型名称，默认为多语言模型
        """
        self.persist_dir = persist_dir
        # 确保持久化目录存在
        Path(self.persist_dir).mkdir(parents=True, exist_ok=True)
        
        # 初始化embedding模型
        try:
            self.embeddings = SentenceTransformerEmbeddings(model_name=model_name)
            print(f"✅ Embedding模型加载成功: {model_name}")
        except Exception as e:
            print(f"❌ Embedding模型初始化失败: {e}")
            raise
            
        # 向量存储缓存，避免重复初始化
        self._vectorstore_cache = {}

    def _collection_name(self, user_id: Optional[int], course_id: Optional[int]) -> str:
        """
        生成集合名称
        
        根据用户ID和课程ID生成唯一的集合名称，实现多租户数据隔离。
        命名规则遵循ChromaDB的命名规范，并按课程维度进行数据隔离。
        
        Args:
            user_id: 用户ID，可选
            course_id: 课程ID，可选
        
        Returns:
            生成的集合名称字符串
        """
        if user_id is not None and course_id is not None:
            return f"user_{user_id}_course_{course_id}"
        if course_id is not None:
            return f"course_{course_id}"
        if user_id is not None:
            return f"user_{user_id}"
        return "global_collection"

    def _vectorstore(self, collection: str) -> Chroma:
        """
        获取向量存储实例（带缓存机制）
        
        提供向量存储实例的单例访问，使用缓存避免重复初始化相同的集合，提高性能。
        
        Args:
            collection: 集合名称
        
        Returns:
            Chroma向量存储实例
        """
        if collection not in self._vectorstore_cache:
            print(f"🔄 初始化向量存储: {collection}")
            # 配置Chroma客户端（如果支持）
            client_settings = None
            if ChromaSettings is not None:
                try:
                    client_settings = ChromaSettings(anonymized_telemetry=False)
                except Exception:
                    client_settings = None
            # 创建并缓存向量存储实例
            self._vectorstore_cache[collection] = Chroma(
                collection_name=collection,
                embedding_function=self.embeddings,
                persist_directory=self.persist_dir,
                client_settings=client_settings
            )
        return self._vectorstore_cache[collection]

    def ingest_file(self, file_path: str, filename: str, user_id: Optional[int], course_id: Optional[int], file_size: int = 0) -> int:
        """
        文档入库处理
        
        执行完整的文档处理流程：解析 -> 分块 -> 向量化 -> 存储到向量数据库
        支持多种文件格式，包括PDF、TXT、DOCX、DOC、MD、JSON和CSV。
        
        Args:
            file_path: 文件的本地路径
            filename: 文件名（包含扩展名）
            user_id: 用户ID，用于数据隔离
            course_id: 课程ID，用于数据隔离
            file_size: 文件大小（字节）
            
        Returns:
            成功处理的文档块数量
            
        Raises:
            ValueError: 当文件格式不支持、内容为空或处理过程中发生错误时抛出
        """
        # 获取文件扩展名并转为小写
        ext = Path(filename).suffix.lower()
        
        try:
            # 根据文件类型选择不同的加载器
            if ext == ".pdf":
                print(f"开始处理PDF文件: {filename}")
                loader = PyPDFLoader(file_path)
                docs = loader.load()
                print(f"PDF加载成功，共{len(docs)}页")
            elif ext == ".txt":
                print(f"开始处理TXT文件: {filename}")
                loader = TextLoader(file_path, encoding="utf-8")
                docs = loader.load()
                print(f"TXT加载成功")
            elif ext in [".docx", ".doc"]:
                print(f"开始处理Word文件: {filename}")
                # 简单的Word文档处理（带备选方案）
                try:
                    import docx2txt
                    content = docx2txt.process(file_path)
                except ImportError:
                    # 如果没有docx2txt库，使用基本的文本提取方法
                    try:
                        with open(file_path, 'rb') as f:
                            content = f.read().decode('utf-8', errors='ignore')
                    except:
                        content = "无法读取Word文档内容"
                
                from langchain.docstore.document import Document
                docs = [Document(page_content=content, metadata={"source": filename})]
                print(f"Word文档加载成功")
            elif ext == ".md":
                print(f"开始处理Markdown文件: {filename}")
                loader = TextLoader(file_path, encoding="utf-8")
                docs = loader.load()
                print(f"Markdown加载成功")
            elif ext == ".json":
                print(f"开始处理JSON文件: {filename}")
                with open(file_path, 'r', encoding='utf-8') as f:
                    import json
                    data = json.load(f)
                    # 格式化JSON内容以便更好地存储和检索
                    content = json.dumps(data, ensure_ascii=False, indent=2)
                    
                from langchain.docstore.document import Document
                docs = [Document(page_content=content, metadata={"source": filename})]
                print(f"JSON加载成功")
            elif ext == ".csv":
                print(f"开始处理CSV文件: {filename}")
                with open(file_path, 'r', encoding='utf-8') as f:
                    content = f.read()
                    
                from langchain.docstore.document import Document
                docs = [Document(page_content=content, metadata={"source": filename})]
                print(f"CSV加载成功")
            else:
                raise ValueError(f"支持的格式: PDF, TXT, DOCX, DOC, MD, JSON, CSV，您上传的是: {ext}")
            
            # 验证文档是否成功加载
            if not docs:
                raise ValueError("文件内容为空或无法读取")
            
            # 检查文档内容是否为空
            total_content = sum(len(doc.page_content) for doc in docs)
            print(f"文档总字符数: {total_content}")
            
            if total_content == 0:
                raise ValueError("文档内容为空")

            # 文档分块处理
            splitter = RecursiveCharacterTextSplitter(chunk_size=800, chunk_overlap=120)
            chunks = splitter.split_documents(docs)
            print(f"文档分块完成，共{len(chunks)}块")
            
            # 添加元数据 - 确保所有值都是字符串（Chroma不接受None或数字）
            # 为整份文档生成唯一稳定的doc_id，便于后续按文档进行筛选和管理
            doc_id = str(uuid.uuid4())
            for chunk in chunks:
                chunk.metadata = chunk.metadata or {}
                # 清理和转换元数据，确保符合Chroma存储要求
                clean_metadata = {
                    "filename": str(filename),
                    "file_type": str(ext),
                    "file_size": str(file_size),
                    "doc_id": doc_id,
                    "source": str(filename)  # Chroma需要的基本字段
                }
                
                # 只有在不是None时才添加用户和课程ID
                if user_id is not None:
                    clean_metadata["user_id"] = str(user_id)
                if course_id is not None:
                    clean_metadata["course_id"] = str(course_id)
                    
                chunk.metadata.update(clean_metadata)

            # 存储到向量库
            collection_name = self._collection_name(user_id, course_id)
            print(f"存储到集合: {collection_name}")
            
            # 清除缓存，确保获取最新的向量存储实例
            if collection_name in self._vectorstore_cache:
                print(f"清除缓存的向量存储实例: {collection_name}")
                del self._vectorstore_cache[collection_name]
            
            vs = self._vectorstore(collection_name)
            
            # 分批添加文档块，避免一次性添加过多导致性能问题
            batch_size = 10
            for i in range(0, len(chunks), batch_size):
                batch = chunks[i:i + batch_size]
                print(f"添加批次 {i//batch_size + 1}: {len(batch)} 个文档块")
                try:
                    vs.add_documents(batch)
                    print(f"批次 {i//batch_size + 1} 添加成功")
                except Exception as e:
                    print(f"批次 {i//batch_size + 1} 添加失败: {e}")
                    raise
            
            # 强制持久化，确保数据保存到磁盘
            try:
                vs.persist()
                print(f"向量数据库持久化完成")
            except Exception as e:
                print(f"向量数据库持久化失败: {e}")
                # 不抛出异常，继续运行
            
            # 验证文档是否真的被添加成功
            try:
                test_results = vs.similarity_search("", k=1)
                print(f"验证: 集合中现有 {len(test_results)} 个文档片段")
                
                # 额外验证：使用底层collection统计
                try:
                    collection = vs._collection
                    count = collection.count()
                    print(f"底层集合统计: {count} 个片段")
                    
                    # 如果统计不一致，列出样本进行调试
                    if count != len(test_results):
                        print(f"⚠️ 统计不一致! similarity_search={len(test_results)}, collection.count={count}")
                        try:
                            sample = collection.get(include=["metadatas"], limit=5)
                            print(f"样本元数据: {sample}")
                        except Exception as sample_e:
                            print(f"获取样本失败: {sample_e}")
                            
                except Exception as count_e:
                    print(f"底层统计失败: {count_e}")
                    
            except Exception as e:
                print(f"验证失败: {e}")
            
            print(f"文档入库成功: {filename}, 共{len(chunks)}块")
            return len(chunks)
            
        except Exception as e:
            print(f"文档处理失败: {filename}, 错误: {str(e)}")
            raise ValueError(f"文档处理失败: {str(e)}")

    def query(self, question: str, user_id: Optional[int], course_id: Optional[int], top_k: int = 4, allowed_doc_ids: Optional[List[str]] = None) -> List[Tuple[str, dict, Optional[float]]]:
        """
        检索相关文档片段
        
        根据用户提问从向量数据库中检索最相关的文档片段，支持按指定doc_id过滤结果。
        当指定集合为空时，会自动尝试使用全局集合进行检索。
        
        Args:
            question: 用户的查询问题
            user_id: 用户ID，用于确定检索范围
            course_id: 课程ID，用于确定检索范围
            top_k: 返回的最大相关片段数量，默认为4
            allowed_doc_ids: 可选的文档ID列表，用于限制检索范围
            
        Returns:
            文档片段列表，每个元素为三元组(text, metadata, score)
            - text: 文档片段文本内容
            - metadata: 文档片段的元数据
            - score: 相关性分数（值越大表示越相关，取决于底层实现），可能为None
        """
        try:
            # 确定要查询的集合名称
            collection_name = self._collection_name(user_id, course_id)
            print(f"查询集合: {collection_name}, 问题: {question}")
            
            # 获取对应的向量存储实例
            vs = self._vectorstore(collection_name)
            
            # 检查集合是否有文档，如果为空则尝试全局集合
            try:
                collection_count = vs._collection.count()
                print(f"当前集合 {collection_name} 文档数量: {collection_count}")
                
                if collection_count == 0 and collection_name != "global_collection":
                    print(f"🔄 当前集合为空，尝试使用全局集合...")
                    vs = self._vectorstore("global_collection")
                    global_count = vs._collection.count()
                    print(f"全局集合文档数量: {global_count}")
                    if global_count > 0:
                        collection_name = "global_collection"
                        print(f"✅ 切换到全局集合进行检索")
            except Exception as e:
                print(f"检查集合状态时出错: {e}")
            
            # 候选结果存储列表
            candidates_with_scores = []
            allowed_set = set(allowed_doc_ids) if allowed_doc_ids else None
            
            # 构建过滤条件（如果指定了文档ID列表）
            if allowed_set:
                try:
                    # 如果底层支持filter，则直接按doc_id过滤，减少无关召回
                    filter_expr = {"doc_id": {"$in": list(allowed_set)}}
                except Exception:
                    filter_expr = None
            else:
                filter_expr = None
            
            # 多级检索策略：优先使用带分数的检索接口
            try:
                # 策略1：使用similarity_search_with_relevance_scores
                if filter_expr is not None:
                    candidates_with_scores = vs.similarity_search_with_relevance_scores(
                        question, k=max(top_k * 5, 10), filter=filter_expr
                    )
                else:
                    candidates_with_scores = vs.similarity_search_with_relevance_scores(
                        question, k=max(top_k * 5, 10)
                    )
            except Exception as _:
                try:
                    # 策略2：降级使用similarity_search_with_score
                    if filter_expr is not None:
                        tmp = vs.similarity_search_with_score(
                            question, k=max(top_k * 5, 10), filter=filter_expr
                        )
                    else:
                        tmp = vs.similarity_search_with_score(
                            question, k=max(top_k * 5, 10)
                        )
                    candidates_with_scores = tmp
                except Exception:
                    # 策略3：最后降级到无分数检索
                    if filter_expr is not None:
                        docs = vs.similarity_search(
                            question, k=max(top_k * 5, 10), filter=filter_expr
                        )
                    else:
                        docs = vs.similarity_search(question, k=max(top_k * 5, 10))
                    candidates_with_scores = [(d, None) for d in docs]
            
            # 处理检索结果，转换为所需的输出格式
            output: List[Tuple[str, dict, Optional[float]]] = []
            for r, score in candidates_with_scores:
                filename = r.metadata.get("filename", "未知文档")
                # 兼容早期未写入doc_id的数据：使用文件名的哈希值作为备用ID
                doc_id = r.metadata.get("doc_id") or str(abs(hash(filename)))
                
                # 如果指定了文档ID过滤，且当前文档ID不在允许列表中，则跳过
                if allowed_set and doc_id not in allowed_set:
                    continue
                
                print(f"检索到文档片段: {filename} ({doc_id}) 分数={score}")
                # 添加到输出列表：(文本内容, 元数据, 相关性分数)
                output.append((r.page_content, r.metadata, float(score) if score is not None else None))
                
                # 如果已达到指定的返回数量上限，则停止处理
                if len(output) >= top_k:
                    break
            
            print(f"总共检索到{len(output)}个相关片段")
            return output
            
        except Exception as e:
            print(f"查询失败: {str(e)}")
            return []
    
    def clear_collection(self, user_id: Optional[int], course_id: Optional[int]):
        """
        清空指定集合的所有文档
        
        使用多级安全策略清空指定集合的所有文档内容，避免因单一方法失败导致操作无法完成。
        当所有自动方法都失败时，会提供手动操作指南。
        
        Args:
            user_id: 用户ID，用于确定要清空的集合
            course_id: 课程ID，用于确定要清空的集合
        """
        try:
            # 确定要清空的集合名称
            collection_name = self._collection_name(user_id, course_id)
            print(f"清空集合: {collection_name}")
            
            # 方法1: 安全清空集合内容（推荐方法）
            try:
                vs = self._vectorstore(collection_name)
                # 获取集合并清空内容
                collection = vs._collection
                
                # 获取所有文档信息
                try:
                    all_docs = collection.get()
                    if all_docs and hasattr(all_docs, 'get') and all_docs.get('ids'):
                        # 分批删除，避免一次性删除太多文档导致性能问题
                        ids = all_docs['ids']
                        batch_size = 100
                        for i in range(0, len(ids), batch_size):
                            batch_ids = ids[i:i + batch_size]
                            collection.delete(ids=batch_ids)
                        print(f"集合 {collection_name} 内容已清空 ({len(ids)} 个文档)")
                    else:
                        print(f"集合 {collection_name} 已经是空的")
                    return
                except Exception as inner_e:
                    print(f"获取文档列表失败: {inner_e}")
                    # 尝试直接删除集合
                    try:
                        vs.delete_collection()
                        print(f"集合 {collection_name} 已删除")
                        return
                    except Exception as delete_e:
                        print(f"删除集合失败: {delete_e}")
                        
            except Exception as e1:
                print(f"方法1失败: {e1}")
            
            # 方法2: 标记清空（备选安全方法）
            try:
                print(f"使用标记清空方法...")
                # 创建一个标记文件，表示该集合已被清空
                clear_marker = Path(self.persist_dir) / f".cleared_{collection_name}"
                clear_marker.touch()
                print(f"集合 {collection_name} 已标记为清空")
                return
                
            except Exception as e2:
                print(f"方法2失败: {e2}")
                
                # 方法3: 输出手动操作指导信息
                print("⚠️ 自动清空失败，请手动操作：")
                print(f"1. 停止AI服务")
                print(f"2. 删除目录: {self.persist_dir}")
                print(f"3. 重启AI服务")
                
                # 不抛出异常，返回成功（让用户手动处理）
                return
                    
        except Exception as e:
            print(f"清空集合失败: {str(e)}")
            # 不抛出异常，允许继续操作
            print("💡 提示：如果需要完全清空，请重启AI服务。")
    
    def list_documents(self, user_id: Optional[int], course_id: Optional[int]) -> List[dict]:
        """
        列出集合中的所有文档
        
        获取指定集合中所有文档的元数据信息，支持通过用户ID和课程ID进行过滤。
        为确保获取最新数据，每次调用都会刷新缓存。
        
        Args:
            user_id: 用户ID，用于确定要查询的集合
            course_id: 课程ID，用于确定要查询的集合
            
        Returns:
            文档信息列表，每个元素为包含文档元数据的字典
        """
        try:
            # 确定要查询的集合名称
            collection_name = self._collection_name(user_id, course_id)
            print(f"📋 列出文档 - 集合: {collection_name}")
            
            # 总是刷新缓存，确保获取最新数据（修复缓存不一致问题）
            if collection_name in self._vectorstore_cache:
                print(f"清除缓存以获取最新文档列表: {collection_name}")
                del self._vectorstore_cache[collection_name]
            
            # 获取向量存储实例
            vs = self._vectorstore(collection_name)
            
            # 存储文档列表结果
            results = []
            
            # 方法1: 直接用similarity_search获取所有文档（简单可靠）
            try:
                print("尝试方法1: similarity_search")
                # 使用空查询获取集合中的所有文档（最多5000个）
                results = vs.similarity_search("", k=5000)  # 空查询获取所有文档
                print(f"方法1成功: 获取到 {len(results)} 个文档片段")
            except Exception as e1:
                print(f"方法1失败: {e1}")
                
                # 方法2: 使用底层collection.get（备选方法）
                try:
                    print("尝试方法2: collection.get")
                    collection = vs._collection
                    total = collection.count()
                    print(f"集合总片段数: {total}")
                    
                    # 调试信息：列出数据库中的所有集合及其文档数量
                    try:
                        client = collection._client
                        all_collections = client.list_collections()
                        print(f"数据库中所有集合: {[c.name for c in all_collections]}")
                        for c in all_collections:
                            try:
                                c_count = c.count()
                                print(f"  - {c.name}: {c_count} 个片段")
                            except:
                                print(f"  - {c.name}: 无法统计")
                    except Exception as debug_e:
                        print(f"调试集合列表失败: {debug_e}")
                    
                    # 如果集合不为空，获取所有文档的元数据和内容
                    if total > 0:
                        raw = collection.get(include=["metadatas", "documents"], limit=total)
                        metadatas = raw.get("metadatas", [])
                        documents = raw.get("documents", [])
                        
                        print(f"获取到 metadatas: {len(metadatas)}, documents: {len(documents)}")
                        
                        # 创建临时内部类来统一数据结构
                        class _R:
                            def __init__(self, content, metadata):
                                self.page_content = content or ""
                                self.metadata = metadata or {}
                        
                        # 构造结果列表
                        results = []
                        for i in range(len(metadatas)):
                            content = documents[i] if i < len(documents) else ""
                            metadata = metadatas[i] if isinstance(metadatas[i], dict) else {}
                            results.append(_R(content, metadata))
                        
                        print(f"方法2成功: 构造了 {len(results)} 个结果对象")
                    else:
                        print("集合为空")
                        results = []
                        
                except Exception as e2:
                    print(f"方法2失败: {e2}")
                    results = []
            
            print(f"最终获取到 {len(results)} 个文档片段")
            
            # 按文档ID/文件名分组统计，合并相同文档的多个片段
            file_stats = {}
            for r in results:
                try:
                    filename = r.metadata.get("filename", "未知文档")
                    file_type = r.metadata.get("file_type", "")
                    
                    # 尝试获取并转换文件大小
                    file_size_str = r.metadata.get("file_size", "0")
                    try:
                        file_size = int(file_size_str) if file_size_str else 0
                    except (ValueError, TypeError):
                        file_size = 0
                    
                    # 获取文档ID，没有则使用文件名生成
                    real_doc_id = r.metadata.get("doc_id")
                    exposed_id = real_doc_id if real_doc_id else f"fn:{filename}"
                    
                    # 按文档ID分组并统计片段数量
                    if exposed_id not in file_stats:
                        file_stats[exposed_id] = {
                            "id": exposed_id,
                            "filename": filename,
                            "file_type": file_type,
                            "file_size": file_size,
                            "chunk_count": 0
                        }
                    file_stats[exposed_id]["chunk_count"] += 1
                    
                except Exception as meta_error:
                    print(f"处理元数据失败: {meta_error}, metadata: {getattr(r, 'metadata', {})}")
                    continue
            
            # 转换为列表格式返回
            doc_list = list(file_stats.values())
            print(f"📋 返回文档列表: {len(doc_list)} 个文档")
            for doc in doc_list:
                print(f"  - {doc['filename']} (id: {doc['id'][:20]}..., chunks: {doc['chunk_count']})")
            
            return doc_list
            
        except Exception as e:
            print(f"❌ 列出文档失败: {str(e)}")
            # 打印详细的错误堆栈信息，便于调试
            import traceback
            traceback.print_exc()
            return []
    
    def delete_document(self, doc_id: str, user_id: Optional[int], course_id: Optional[int]) -> bool:
        """
        删除指定文档的所有片段
        
        根据文档ID从指定集合中删除该文档的所有片段。支持两种文档标识方式：
        1. 标准文档ID (doc_id)
        2. 文件名标识 (格式: fn:文件名，用于兼容早期没有doc_id的数据)
        
        Args:
            doc_id: 文档唯一标识符或文件名标识（格式：fn:文件名）
            user_id: 用户ID，用于确定要操作的集合
            course_id: 课程ID，用于确定要操作的集合
            
        Returns:
            bool: 删除是否成功
        """
        try:
            # 确定要操作的集合名称
            collection_name = self._collection_name(user_id, course_id)
            print(f"🗑️ 尝试删除文档 {doc_id} 从集合 {collection_name}")
            
            # 清除缓存，确保获取最新数据
            if collection_name in self._vectorstore_cache:
                print(f"清除缓存的向量存储实例: {collection_name}")
                del self._vectorstore_cache[collection_name]
            
            # 获取向量存储实例
            vs = self._vectorstore(collection_name)
            collection = getattr(vs, "_collection", None)
            
            # 检查是否成功获取底层集合对象
            if collection is None:
                print("❌ 无法获取底层集合对象")
                return False
            
            # 组装过滤条件：优先按doc_id；若id以fn:开头，则按文件名删除（兼容早期无doc_id数据）
            meta_filter = {"doc_id": {"$eq": doc_id}}
            if doc_id.startswith("fn:"):
                filename = doc_id[3:]
                meta_filter = {"filename": {"$eq": filename}}
            
            try:
                # 记录删除前的文档数量，用于验证删除是否成功
                before = collection.count()
                
                # 尝试通过底层collection接口删除
                try:
                    collection.delete(where=meta_filter)
                except Exception:
                    # 降级：尝试使用向量库封装的删除方法
                    try:
                        vs.delete(where=meta_filter)
                    except Exception as _:
                        pass
                
                # 记录删除后的文档数量
                after = collection.count()
                
                # 验证删除是否成功
                if after < before:
                    # 尝试持久化更改
                    try:
                        vs.persist()
                    except Exception:
                        pass
                    print(f"✅ 文档删除成功，数量 {before}->{after}")
                    return True
                else:
                    print(f"❌ 未找到匹配的片段，filter={meta_filter}")
                    return False
            except Exception as delete_error:
                print(f"❌ 删除过程中出错: {delete_error}")
                return False
                
        except Exception as e:
            print(f"❌ 删除文档失败: {str(e)}")
            return False
